// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: System.cpp
//

//
// Purpose: Native methods on System.Environment & Array
//

//

#include "common.h"

#include <object.h>

#include "ceeload.h"

#include "excep.h"
#include "frames.h"
#include "vars.hpp"
#include "classnames.h"
#include "system.h"
#include "string.h"
#include "sstring.h"
#include "eeconfig.h"
#include "assemblynative.hpp"
#include "generics.h"
#include "invokeutil.h"
#include "array.h"
#include "eepolicy.h"


#ifdef FEATURE_WINDOWSPHONE
Volatile<BOOL> g_fGetPhoneVersionInitialized;

// This is the API to query the phone version information
typedef BOOL (*pfnGetPhoneVersion)(LPOSVERSIONINFO lpVersionInformation);

pfnGetPhoneVersion g_pfnGetPhoneVersion = NULL;
#endif

typedef void(WINAPI *pfnGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime);
extern pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime;

void WINAPI InitializeGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime)
{
    pfnGetSystemTimeAsFileTime func = NULL;

#ifndef FEATURE_PAL
    HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll"));
    if (hKernel32 != NULL)
    {
        func = (pfnGetSystemTimeAsFileTime)GetProcAddress(hKernel32, "GetSystemTimePreciseAsFileTime");
    }
    if (func == NULL)
#endif
    {
        func = &::GetSystemTimeAsFileTime;
    }
    
    g_pfnGetSystemTimeAsFileTime = func;
    func(lpSystemTimeAsFileTime);
}

pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime = &InitializeGetSystemTimeAsFileTime;

FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
{
    FCALL_CONTRACT;

    INT64 timestamp;
    g_pfnGetSystemTimeAsFileTime((FILETIME*)&timestamp);

#if BIGENDIAN
    timestamp = (INT64)(((UINT64)timestamp >> 32) | ((UINT64)timestamp << 32));
#endif

    return timestamp;
}
FCIMPLEND;



FCIMPL0(UINT32, SystemNative::GetCurrentProcessorNumber)
{
    FCALL_CONTRACT;

    return ::GetCurrentProcessorNumber();
}
FCIMPLEND;



FCIMPL0(UINT32, SystemNative::GetTickCount)
{
    FCALL_CONTRACT;
    
    return ::GetTickCount();
}
FCIMPLEND;




VOID QCALLTYPE SystemNative::Exit(INT32 exitcode)
{
    QCALL_CONTRACT;

    BEGIN_QCALL;

    // The exit code for the process is communicated in one of two ways.  If the
    // entrypoint returns an 'int' we take that.  Otherwise we take a latched
    // process exit code.  This can be modified by the app via setting
    // Environment's ExitCode property.
    SetLatchedExitCode(exitcode);

    ForceEEShutdown();

    END_QCALL;
}

FCIMPL1(VOID,SystemNative::SetExitCode,INT32 exitcode)
{
    FCALL_CONTRACT;
    
    // The exit code for the process is communicated in one of two ways.  If the
    // entrypoint returns an 'int' we take that.  Otherwise we take a latched
    // process exit code.  This can be modified by the app via setting
    // Environment's ExitCode property.
    SetLatchedExitCode(exitcode);
}
FCIMPLEND

FCIMPL0(INT32, SystemNative::GetExitCode)
{
    FCALL_CONTRACT;
    
    // Return whatever has been latched so far.  This is uninitialized to 0.
    return GetLatchedExitCode();
}
FCIMPLEND

void QCALLTYPE SystemNative::_GetCommandLine(QCall::StringHandleOnStack retString)
{
    QCALL_CONTRACT;

    BEGIN_QCALL;

    LPCWSTR commandLine;

    if (g_pCachedCommandLine != NULL)
    {
        // Use the cached command line if available
        commandLine = g_pCachedCommandLine;
    }
    else
    {
        commandLine = WszGetCommandLine();
        if (commandLine==NULL)
            COMPlusThrowOM();
    }
    
    retString.Set(commandLine);

    END_QCALL;
}

FCIMPL0(Object*, SystemNative::GetCommandLineArgs)
{
    FCALL_CONTRACT;

    PTRARRAYREF strArray = NULL;

    HELPER_METHOD_FRAME_BEGIN_RET_1(strArray);

    LPWSTR commandLine;

    if (g_pCachedCommandLine != NULL)
    {
        // Use the cached command line if available
        commandLine = g_pCachedCommandLine;
    }
    else
    {
        commandLine = WszGetCommandLine();
        if (commandLine==NULL)
            COMPlusThrowOM();
    }

    DWORD numArgs = 0;
    LPWSTR* argv = SegmentCommandLine(commandLine, &numArgs);
    if (!argv)
        COMPlusThrowOM();

    _ASSERTE(numArgs > 0);
    
    strArray = (PTRARRAYREF) AllocateObjectArray(numArgs, g_pStringClass);
    // Copy each argument into new Strings.
    for(unsigned int i=0; i<numArgs; i++) 
    {
        STRINGREF str = StringObject::NewString(argv[i]);
        STRINGREF * destData = ((STRINGREF*)(strArray->GetDataPtr())) + i;
        SetObjectReference((OBJECTREF*)destData, (OBJECTREF)str, strArray->GetAppDomain());
    }
    delete [] argv;

    HELPER_METHOD_FRAME_END();

    return OBJECTREFToObject(strArray); 
}
FCIMPLEND


FCIMPL1(FC_BOOL_RET, SystemNative::_GetCompatibilityFlag, int flag)
{
    FCALL_CONTRACT;

    FC_RETURN_BOOL(GetCompatibilityFlag((CompatibilityFlag)flag));
}
FCIMPLEND

// Note: Arguments checked in IL.
FCIMPL1(Object*, SystemNative::_GetEnvironmentVariable, StringObject* strVarUNSAFE)
{
    FCALL_CONTRACT;

    STRINGREF refRetVal;
    STRINGREF strVar;

    refRetVal   = NULL;
    strVar      = ObjectToSTRINGREF(strVarUNSAFE);

    HELPER_METHOD_FRAME_BEGIN_RET_2(refRetVal, strVar);

    int len;

    // Get the length of the environment variable.
    PathString envPath;    // prefix complains if pass a null ptr in, so rely on the final length parm instead
    len = WszGetEnvironmentVariable(strVar->GetBuffer(), envPath);

    if (len != 0)
    {
        // Allocate the string.
        refRetVal = StringObject::NewString(len);
 
        wcscpy_s(refRetVal->GetBuffer(), len + 1, envPath);
        
    }

    HELPER_METHOD_FRAME_END();

    return OBJECTREFToObject(refRetVal);
}
FCIMPLEND

// Return a method info for the method were the exception was thrown
FCIMPL1(ReflectMethodObject*, SystemNative::GetMethodFromStackTrace, ArrayBase* pStackTraceUNSAFE)
{
    FCALL_CONTRACT;
    
    I1ARRAYREF pArray(static_cast<I1Array *>(pStackTraceUNSAFE));
    StackTraceArray stackArray(pArray);

    if (!stackArray.Size())
        return NULL;

    // The managed stacktrace classes always returns typical method definition, so we don't need to bother providing exact instantiation.
    // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(pElements[0].pFunc, pElements[0].pExactGenericArgsToken, pTypeHandle, &pMD);

    MethodDesc* pFunc = stackArray[0].pFunc;

    // Strip the instantiation to make sure that the reflection never gets a bad method desc back.
    REFLECTMETHODREF refRet = NULL;

    HELPER_METHOD_FRAME_BEGIN_RET_0()
    pFunc = pFunc->LoadTypicalMethodDefinition();
    refRet = pFunc->GetStubMethodInfo();
    _ASSERTE(pFunc->IsRuntimeMethodHandle());

    HELPER_METHOD_FRAME_END();

    return (ReflectMethodObject*)OBJECTREFToObject(refRet);
}
FCIMPLEND

FCIMPL0(StringObject*, SystemNative::_GetModuleFileName)
{
    FCALL_CONTRACT;

    STRINGREF   refRetVal = NULL;

    HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal);
    if (g_pCachedModuleFileName)
    {
        refRetVal = StringObject::NewString(g_pCachedModuleFileName);
    }
    else
    {
        PathString wszFilePathString;

       
        DWORD lgth = WszGetModuleFileName(NULL, wszFilePathString);
        if (!lgth)
        {
            COMPlusThrowWin32();
        }
       

        refRetVal = StringObject::NewString(wszFilePathString.GetUnicode());
    }
    HELPER_METHOD_FRAME_END();

    return (StringObject*)OBJECTREFToObject(refRetVal);
}
FCIMPLEND

FCIMPL0(StringObject*, SystemNative::GetRuntimeDirectory)
{
    FCALL_CONTRACT;

    STRINGREF   refRetVal   = NULL;
    DWORD dwFile = MAX_LONGPATH+1;

    HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal);
    SString wszFilePathString;

    WCHAR * wszFile = wszFilePathString.OpenUnicodeBuffer(dwFile);
    HRESULT hr = GetInternalSystemDirectory(wszFile, &dwFile);
    wszFilePathString.CloseBuffer(dwFile);
    
    if(FAILED(hr))
        COMPlusThrowHR(hr);

    dwFile--; // remove the trailing NULL

    if(dwFile)
        refRetVal = StringObject::NewString(wszFile, dwFile);

    HELPER_METHOD_FRAME_END();
    return (StringObject*)OBJECTREFToObject(refRetVal);
}
FCIMPLEND

INT32 QCALLTYPE SystemNative::GetProcessorCount()
{
    QCALL_CONTRACT;

    INT32 processorCount = 0;

    BEGIN_QCALL;

    CPUGroupInfo::EnsureInitialized();

    if(CPUGroupInfo::CanEnableThreadUseAllCpuGroups())
    {
        processorCount = CPUGroupInfo::GetNumActiveProcessors();
    }

    // Processor count will be 0 if CPU groups are disabled/not supported
    if(processorCount == 0)
    {
        SYSTEM_INFO systemInfo;
        ZeroMemory(&systemInfo, sizeof(systemInfo));

        GetSystemInfo(&systemInfo);

        processorCount = systemInfo.dwNumberOfProcessors;
    }

    END_QCALL;

    return processorCount;
}

FCIMPL0(FC_BOOL_RET, SystemNative::HasShutdownStarted)
{
    FCALL_CONTRACT;

    // Return true if the EE has started to shutdown and is now going to 
    // aggressively finalize objects referred to by static variables OR
    // if someone is unloading the current AppDomain AND we have started
    // finalizing objects referred to by static variables.
    FC_RETURN_BOOL((g_fEEShutDown & ShutDown_Finalize2) || GetAppDomain()->IsFinalizing());
}
FCIMPLEND

// FailFast is supported in BCL.small as internal to support failing fast in places where EEE used to be thrown.
//
// Static message buffer used by SystemNative::FailFast to avoid reliance on a
// managed string object buffer. This buffer is not always used, see comments in
// the method below.
WCHAR g_szFailFastBuffer[256];
#define FAIL_FAST_STATIC_BUFFER_LENGTH (sizeof(g_szFailFastBuffer) / sizeof(WCHAR))

// This is the common code for FailFast processing that is wrapped by the two
// FailFast FCalls below.
void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, UINT_PTR retAddress, UINT exitCode)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }CONTRACTL_END;

    struct 
    {
        STRINGREF refMesgString;
        EXCEPTIONREF refExceptionForWatsonBucketing;
    } gc;
    ZeroMemory(&gc, sizeof(gc));

    GCPROTECT_BEGIN(gc);
    
    gc.refMesgString = refMesgString;
    gc.refExceptionForWatsonBucketing = refExceptionForWatsonBucketing;

    // Managed code injected FailFast maps onto the unmanaged version
    // (EEPolicy::HandleFatalError) in the following manner: the exit code is
    // always set to COR_E_FAILFAST and the address passed (usually a failing
    // EIP) is in fact the address of a unicode message buffer (explaining the
    // reason for the fault).
    // The message string comes from a managed string object so we can't rely on
    // the buffer remaining in place below our feet. But equally we don't want
    // to inject failure points (by, for example, allocating a heap buffer or a
    // pinning handle) when we have a much higher chance than usual of actually
    // tripping those failure points and eradicating useful debugging info.
    // We employ various strategies to deal with this:
    //   o  If the message is small enough we copy it into a static buffer
    //      (g_szFailFastBuffer).
    //   o  Otherwise we try to allocate a buffer of the required size on the
    //      heap. This buffer will be leaked.
    //   o  If the allocation above fails we return to the static buffer and
    //      truncate the message.
    //
    // Another option would seem to be to implement a new frame type that
    // protects object references as pinned, but that seems like overkill for
    // just this problem.
    WCHAR  *pszMessage = NULL;
    DWORD   cchMessage = (gc.refMesgString == NULL) ? 0 : gc.refMesgString->GetStringLength();

    if (cchMessage < FAIL_FAST_STATIC_BUFFER_LENGTH)
    {
        pszMessage = g_szFailFastBuffer;
    }
    else
    {
        // We can fail here, but we can handle the fault.
        CONTRACT_VIOLATION(FaultViolation);
        pszMessage = new (nothrow) WCHAR[cchMessage + 1];
        if (pszMessage == NULL)
        {
            // Truncate the message to what will fit in the static buffer.
            cchMessage = FAIL_FAST_STATIC_BUFFER_LENGTH - 1;
            pszMessage = g_szFailFastBuffer;
        }
    }
    
    if (cchMessage > 0)
        memcpyNoGCRefs(pszMessage, gc.refMesgString->GetBuffer(), cchMessage * sizeof(WCHAR));
    pszMessage[cchMessage] = W('\0');

    if (cchMessage == 0) {
        WszOutputDebugString(W("CLR: Managed code called FailFast without specifying a reason.\r\n"));
    }
    else {
        WszOutputDebugString(W("CLR: Managed code called FailFast, saying \""));
        WszOutputDebugString(pszMessage);
        WszOutputDebugString(W("\"\r\n"));
    }

    Thread *pThread = GetThread();

#ifndef FEATURE_PAL    
    // If we have the exception object, then try to setup
    // the watson bucket if it has any details.
    // On CoreCLR, Watson may not be enabled. Thus, we should
    // skip this, if required.
    if (IsWatsonEnabled())
    {
        BEGIN_SO_INTOLERANT_CODE(pThread);
        if ((gc.refExceptionForWatsonBucketing == NULL) || !SetupWatsonBucketsForFailFast(gc.refExceptionForWatsonBucketing))
        {
            PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker();
            _ASSERTE(pUEWatsonBucketTracker != NULL);
            pUEWatsonBucketTracker->SaveIpForWatsonBucket(retAddress);
            pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::FatalError, pThread, NULL);
            if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)
            {
                pUEWatsonBucketTracker->ClearWatsonBucketDetails();
            }
        }
        END_SO_INTOLERANT_CODE;
    }
#endif // !FEATURE_PAL

    // stash the user-provided exception object. this will be used as
    // the inner exception object to the FatalExecutionEngineException.
    if (gc.refExceptionForWatsonBucketing != NULL)
        pThread->SetLastThrownObject(gc.refExceptionForWatsonBucketing);

    EEPolicy::HandleFatalError(exitCode, retAddress, pszMessage);

    GCPROTECT_END();
}

// Note: Do not merge this FCALL method with any other FailFast overloads.
// Watson uses the managed FailFast method with one String for crash dump bucketization. 
FCIMPL1(VOID, SystemNative::FailFast, StringObject* refMessageUNSAFE)
{   
    FCALL_CONTRACT;

    STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
    
    HELPER_METHOD_FRAME_BEGIN_1(refMessage);

    // The HelperMethodFrame knows how to get the return address.
    UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
    
    // Call the actual worker to perform failfast
    GenericFailFast(refMessage, NULL, retaddr, COR_E_FAILFAST);

    HELPER_METHOD_FRAME_END();
}
FCIMPLEND

FCIMPL2(VOID, SystemNative::FailFastWithExitCode, StringObject* refMessageUNSAFE, UINT exitCode)
{   
    FCALL_CONTRACT;

    STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
    
    HELPER_METHOD_FRAME_BEGIN_1(refMessage);

    // The HelperMethodFrame knows how to get the return address.
    UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
    
    // Call the actual worker to perform failfast
    GenericFailFast(refMessage, NULL, retaddr, exitCode);

    HELPER_METHOD_FRAME_END();
}
FCIMPLEND

FCIMPL2(VOID, SystemNative::FailFastWithException, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE)
{   
    FCALL_CONTRACT;

    STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
    EXCEPTIONREF refException = (EXCEPTIONREF)refExceptionUNSAFE;

    HELPER_METHOD_FRAME_BEGIN_2(refMessage, refException);

    // The HelperMethodFrame knows how to get the return address.
    UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
    
    // Call the actual worker to perform failfast
    GenericFailFast(refMessage, refException, retaddr, COR_E_FAILFAST);

    HELPER_METHOD_FRAME_END();
}
FCIMPLEND

FCIMPL0(FC_BOOL_RET, SystemNative::IsServerGC)
{
    FCALL_CONTRACT;

    FC_RETURN_BOOL(GCHeapUtilities::IsServerHeap());
}
FCIMPLEND

#ifdef FEATURE_COMINTEROP

BOOL QCALLTYPE SystemNative::WinRTSupported()
{
    QCALL_CONTRACT;

    BOOL hasWinRT = FALSE;

    BEGIN_QCALL;
    hasWinRT = ::WinRTSupported();
    END_QCALL;

    return hasWinRT;
}

#endif // FEATURE_COMINTEROP




	


